容器云平台的备份与恢复技术
【作者】陈强,目前就职于上汽集团云计算中心,容器云架构师及技术经理;长年在云计算领域搬砖,曾就职于Intel, IBM, 爱奇艺 等公司;有五年基于Docker/Mesos/Kubernetes的云容器研发经验,积累了丰富的生产实践经验,专注于云原生技术的研究。
容器云平台本身是一个非常复杂的底层基础设施平台,它主要用于实现业务的持续集成与发布、自动部署、滚动升级、自动扩缩容、租户管理、安全认证等功能模块。这些都是为了实现业务的易用、易发布、强安全等。如何能保证自身的高可用性,这将是衡量用户体验的关键指标,比如在其所依赖的存储、网络等物理设备出现故障时,如何保证其上运行的业务的高可用,无中断或者用户无感知,就需要通过有效的备份与容灾等技术手段来实现。
大家都知道,容器云平台包括容器底层的容器编排系统及自身的平台软件系统。底层可以使用多种容器编排系统,开源的主要有Mesos、Swarm及Kubernetes。这里主要讲解 Kubernetes(K8S)容器编排集群的备份与恢复,软件平台本身则通过K8S来实现。
我们接下来,会从K8S平台架构开始,通过介绍平台架构,让我们了解平台在故障时,哪些组件才是影响服务的高可用性及恢复性的关键,并分享相关的备份恢复与容灾技术。
1 K8S平台架构
Kubernetes(K8S)是用来进行对业务容器实现有效的高可用性的容器编排系统,并可以用来管理集群节点,来按需分配CPU、内存等资源给相关的业务容器。既然是管理集群,那么就存在被管理节点,针对每个Kubernetes集群都由3~5个Master做为高可用来负责管理和控制集群节点。我们通过Master对每个节点Node发送命令来实现业务容器的运行。简单来说,Master就是管理者,Node就是被管理者。Node可以是一台物理机器或者一台虚拟机。在Node上面可以运行多个Pod,Pod是Kubernetes管理的最小单位,同时每个Pod可以包含多个容器。
通过下面的Kubernetes架构简图可以看到Master和Node之间的关系及底层etcd节点的关系。
从K8S架构来看,底层是以etcd为键值数据库,它是K8S的大脑,集群中所有的数据都存储在其中。上层是各系统组件,本身是一个分布式系统构件,各自通过代码实现了高可用,可以暂时不用考虑数据的备份。所有K8S数据都在etcd中,所以etcd是一个我们需要重点关注的备份点。
上图是K8S主要的工作负载(workloads),都是以Pod为基础进行封装与扩展的资源对象,比如:用于在每台主机上最多只部署一个副本的DaemonSet,可以多副本控制的ReplicaSet,及其上的可以用于控制滚动升降级的Deployment用于部署有状态应用的StatefulSet,用于完成一次性作业就退出的Job及基于其扩展的控制定时作业的CronJob,还有用于控制副本数量的ReplicationController;用于暴露内部服务,负责内部流量负载均衡的Service,用于水平和横向扩展Pod副本及CPU/Mem资源的控制器HPA/VPA,用于限制在同一时间自愿中断的复制应用程序中宕机的Pod的数量的Pod DisruptionBudget(Pod中断预算);用于控制有状态服务或提供数据挂卷服务的Volume,及基于Volume扩展的用于提供业务应用配置信息的ConfigMap,用于传递及保存密钥的Secret,用于提供持久卷申请的PVC,用于将Host或者Pod信息引用给容器的DownwardAPI,用于Host中本地临时目录做数据卷的HostPath/EmptyDir等等。这些workloads也就是一些提供给用户使用的资源类型或对像实体,它是业务(比如Tomcat服务)的承载体,用户通过kubectl或者client-go等RestfulAPI接口给Master中的APIServer下发部署命令。这些workloads通常以“.yaml”结尾的配置文件,主要包含副本的类型、副本个数、名称、端口、模版等信息。APIServer接受到请求以后,会分别进行以下操作:
◎ 权限验证(包括特殊控制),取出需要创建的资源,保存副本信息到etcd。
◎ APIServer和ControllerManager,Scheduler以及kubelete之间通过List-Watch方式通信(事件发送与监听)。
◎ ControllerManager通过etcd获取需要创建资源的副本数,交由Scheduler进行调度策略分析。
◎ kubelet负责最终的Pod创建和容器加载。部署好容器(比如:Deployment,StatefulSet)以后,通过Service进行访问,通过cAdvisor监控资源使用情况。
◎ 最后以L4或L7负载均衡的方式暴露于集群外供用户访问。
所以,当业务以不同的workloads运行在K8S上时,这些业务的稳定性及数据安全性,就是依赖于K8S来维护其高可用的,当这些业务被误删时,我们需要能完整的进行还原。一般通过备份这些业务的yaml部署文件,有状态业务的数据,之后进行重建与数据迁移、恢复等方式来保证业务的还原,所以对于这些业务的配置yaml文件及相关的依赖关系,也是我们本节要讲的另一个重要的备份与恢复点。
2 Etcd备份与恢复
Etcd是一个为分布式系统提供关键的高可靠的键值存储服务,聚集于简单、安全、快速、可靠等应用场景。它是Kubernetes集群的大脑,存储了集群所有的数据信息。在Kubernetes中etcd可以通过Raft分布式共识算法来交换信息,并且可以为分布式系统安全存储关键数据。所以如果发生灾难或者etcd的数据丢失时,都会影响集群数据的恢复。
etcd集群与zoomkeeper集群相似,一般采用奇数设备来搭建集群,最多只允许有(N-1)/2的故障点,即假设集群数量N是3台设备,最多只能允许1台设备出现故障,而还剩余2台,这样将不影响集群的可用性。
etcd存放的数据如此重要,那我们备份就需要做到万无一失。我们采用本地备份、远程备份。具体方案如下:
◎ 一重备份:本地会每隔 6 小时备份一次,并保留最近 28 个备份,也就是 1 周的数据。
◎ 二重备份:远程备份一个月或更长时间段的数据,并定期清理。
备份在S3中的存储格式如下:
本地使用某个etcd-backups文件目录存放一周数据,远程使用 S3存储。在S3中,存放的目录的组织方式有些讲究,我们可以将某个S3bucket规划为K8S集群所有,每个集群都对应其中的一个以集群名命名的子目录,每个子目录下备份的又是当前etcd节点的db文件。比如s3://k8s-cluster/shjq-prod-a/10.129.122.15/这个表示当前etcd节点是10.129.122.15,它的数据会被存放到S3集群中的以k8s-cluster命名的bucket下面,该etcd属于shjq-prod-a环境,所以将其作为子目录,最后以自身节点来命名文件夹,这样数据就相应的备份好了。这种命名规范很重要,当有多个K8S集群的数据需要备份时,我们可以在恢复时,很容易找到备份文件进行还原,这也特别适合自动化脚本的编写。
对于OpenShift4,不要在第一个证书轮转完成前(安装后的24小时内)进行etcd备份,否则备份将包含过期的证书。另外,建议您在非使用高峰时段对etcd进行备份,因为备份可能会影响到系统性能。
您可以在任何运行etcd实例的master主机上执行etcd数据备份(不需要为每个master主机进行备份),也可以通过自动化来定期进行etcd的备份。
1. 为 master 节点启动一个 debug 会话:
$ oc debug node/<node_name>
2. 将您的根目录改为主机:
sh-4.2# chroot /host
3.(optional)如果启用了集群范围的代理,请确定已导出了NO_PROXY、HTTP_PROXY和 HTTPS_PROXY环境变量。
4. 运行cluster-backup.sh脚本,输入保存备份的位置。
sh-4.4# /usr/local/bin/cluster-backup.sh /home/core/assets/backup
在这个示例中,在master主机上的/home/core/assets/backup/目录中创建了两个文件:
◎ snapshot_<datetimestamp>.db:这个文件是etcd快照
◎ static_kuberesources_<datetimestamp>.tar.gz:此文件包含静态Pod的资源。如果启用了etcd加密,它也包含 etcd 快照的加密密钥。
如果启用了etcd加密,建议出于安全考虑,将第二个文件与etcd快照分开保存。但是,需要这个文件才能从etcd快照中进行恢复。
etcd仅对值进行加密,而不对键进行加密。这意味着资源类型、命名空间和对象名称是不加密的。
◎ 替换机器没有运行或者节点未就绪的不健康的etcd成员
使用cluster-admin,选择一个不在受影响节点上的Pod
连接到正在运行的etcd容器
oc rsh -n openshift-etcd etcd-ip-10-0-154-204.ec2.internal
查看成员列表:
sh-4.2# etcdctl member list -w table
通过向etcdctl member remove命令提供ID来删除不健康的etcd成员
sh-4.2# etcdctl member remove 6fc1e7c9db35841d
确认删除
sh-4.2# etcdctl member list -w table
◎ 替换etcd pod处于crashlooping状态的不健康etcd成员
◎ 恢复到以前的集群状态
2.2 Etcd恢复
当etcd集群的leader故障时,etcd集群会自动选择一个新leader。在leader选举期间,集群不能处理任何写入操作。在选举期间发送的写入请求需要排队等待处理,直到选出新的 leader。已经发送给oldleader但尚未提交的数据可能会丢失。新leader有权重写oldleader的任何未提交的条目。从用户的角度来看,一些写入请求可能会超时,但是没有提交的写入会丢失。
为了从灾难性故障中恢复,etcdv3提供了快照和恢复功能,以便在没有v3密钥数据丢失的情况下重新创建群集。要恢复集群,只需要一个快照“db”文件。集群恢复将创建新的etcd数据目录; 所有成员应该使用相同的快照进行恢复。恢复会覆盖某些快照元数据(特别是成员ID和群集ID),该成员失去了其以前的身份,该元数据覆盖可防止新成员无意中加入现有群集。因此,为了从快照启动集群,还原必须重启etcd 集群。
在还原时可以选择验证快照完整性。如果使用快照etcdctlsnapshotsave,它将由通过完整性散列检查的etcdctl snapshot restore来恢复。如果快照是从数据目录复制的(配置文件中的data-dir),则不存在完整性哈希,并且只能使用--skip-hash-check来恢复。
以我们下面的生产环境脚本为例,etcd以docker方式运行,并需要在恢复前,提前清理当前etcd数据目录下的数据,并借助snapshot功能,使用备份的db文件进行restore,最后重启etcd服务,以使当前数据恢复到我们给定的某个时刻。当然这恢复操作,需要在不同的节点上依次进行,来保证恢复后的etcd集群正常运行。
Kubernetes集群备份主要是备份etcd集群,在恢复时,根据组件之间的依赖关系,整个恢复流程的顺序如下:
◎ 停止kube-apiserver组件,关闭集群数据写入
◎ 停止etcd节点,准备替换底层db数据
◎ 恢复数据snapshot restore,用需要回滚的db开始替换现存的数据
◎ 启动etcd,重新加载新的数据库信息
◎ 启动kube-apiserver组件,开启集群数据写入
注意:备份etcd集群时,只需要备份一个etcd就行,恢复时,拿同一份备份数据恢复。
对于OpenShift4,您可以使用已保存的etcd备份恢复到先前的集群状态。您可以使用etcd备份来恢复单个control plane主机。然后,etcd cluster Operator会处理剩余的master主机的扩展。
流程如下:
1. 选择一个要用作恢复主机的control plane 主机。这是您要在其中运行恢复操作的主机。
2. 建立到每个control plane节点(包括恢复主机)的SSH连接。恢复过程启动后,Kubernetes API服务器将无法访问,因此您无法访问control plane节点。因此,建议在一个单独的终端中建立到每个control plane主机的SSH连接。
3. 将etcd备份目录复制复制到恢复control plane主机上。此流程假设您将backup目录(其中包含etcd快照和静态Pod资源)复制到恢复control plane主机的/ home/core/ 目录中
4. 在所有其他control plane节点上停止静态Pod
◎ 将现有 etcd Pod 文件从 Kubelet 清单目录中移出:
◎ 验证 etcd Pod 是否已停止。
◎ 将现有 Kubernetes API 服务器 Pod 文件移出 kubelet 清单目录:
◎ 验证 Kubernetes API 服务器 Pod 是否已停止。
◎ 将 etcd 数据目录移到不同的位置:
◎ 在其他不是恢复主机的 master 主机上重复这个步骤。
5. 访问恢复control plane主机。
6. (optional)如果启用了集群范围的代理,请确定已导出了NO_PROXY、HTTP_PROXY和HTTPS_PROXY环境变量。
7. 在恢复control plane主机上运行恢复脚本,提供到etcd备份目录的路径:
[core@ip-10-0-143-125 ~]$ sudo -E /usr/local/bin/cluster-restore.sh /home/core/backup
8. 在所有master主机上重启kubelet服务。
[core@ip-10-0-143-125 ~]$ sudo systemctl restart kubelet.service
9. 确认单个成员control plane已被成功启动。
◎ 从恢复主机上,验证 etcd 容器是否正在运行。
[core@ip-10-0-143-125 ~]$ sudo crictl ps | grep etcd
◎ 从恢复主机上,验证etcd Pod是否正在运行。
[core@ip-10-0-143-125 ~]$ oc get pods -n openshift-etcd | grep etcd
如果状态是Pending,或者输出中列出了多个正在运行的etcd Pod,请等待几分钟,然后再次检查。
10. 强制etcd重新部署。
oc patch etcd cluster -p='{"spec": {"forceRedeploymentReason": "recovery-'"$( date --rfc-3339=ns )"'"}}' --type=merge
11. 验证所有节点是否已更新至最新的修订版本。
oc get etcd -
o=jsonpath='{range .items[0].status.conditions[?(@.type=="NodeInstallerProgressing")]}{.reason}{"\n"}{.message}{"\n"}'
返回:AllNodesAtLatestRevision
3 nodes are at revision 3
12. 在重新部署 etcd 后,为 control plane 强制进行新的 rollout
◎ 更新kubeapiserver:
$ oc patch kubeapiserver cluster -p='{"spec": {"forceRedeploymentReason": "recovery-'"$( date --rfc-3339=ns )"'"}}' --type=merge=
验证所有节点是否已更新至最新的修订版本。
$ oc get kubeapiserver -
o=jsonpath='{range .items[0].status.conditions[?(@.type=="NodeInstallerProgressing")]}{.reason}{"\n"}{.message}{"\n"}'
返回:AllNodesAtLatestRevision
3 nodes are at revision 3
◎ 更新kubecontroller manager:
$ oc patch kubecontrollermanager cluster -p='{"spec": {"forceRedeploymentReason": "recovery-'"$( date --rfc-3339=ns )"'"}}' --type=merge
验证所有节点是否已更新至最新的修订版本。
$ oc get kubecontrollermanager -
o=jsonpath='{range .items[0].status.conditions[?(@.type=="NodeInstallerProgressing")]}{.reason}{"\n"}{.message}{"\n"}'
返回:AllNodesAtLatestRevision
3 nodes are at revision 3
◎ 更新 kubescheduler:
$ oc patch kubescheduler cluster -p='{"spec": {"forceRedeploymentReason": "recovery-'"$( date --rfc-3339=ns )"'"}}' --type=merge
验证所有节点是否已更新至最新的修订版本。
$ oc get kubescheduler -
o=jsonpath='{range .items[0].status.conditions[?(@.type=="NodeInstallerProgressing")]}{.reason}{"\n"}{.message}{"\n"}'
返回:AllNodesAtLatestRevision
3 nodes are at revision 3
13. 验证所有master主机已启动并加入集群。
$ oc get pods -n openshift-etcd | grep etcd
etcd-ip-10-0-143-125.ec2.internal 2/2 Running 0 9h
etcd-ip-10-0-154-194.ec2.internal 2/2 Running 0 9h
etcd-ip-10-0-173-171.ec2.internal 2/2 Running 0 9h
3 k8s workloads备份与恢复
前面我们讲过了workloads,它是各种k8sresource的总和,是k8s承载业务的载体,主要有Deploy-ment,StatefulSet,DaemonSet等,这些workloads在部署之前,通常会通过yaml模板文件等来配置容器镜像,端口,副本数,Service,Ingress等资源,这些一起构成了一个完整的业务部署形式,所以我们需要在当业务被误删或需要迁移等时,就需要提前做好备份。
比如我们需要备份
◎ 应用版本信息(Application Version)
• 应用对应的工作负载,例如:Deployment,StatefulSet以及Daemonset等等
• 应用需要使用的配置,例如:ConfigMap,Secret等等
◎ 应用状态信息(Application State)
• 应用可以分为有状态应用和无状态应用
• 有状态应用需要备份状态数据,例如:database
目前,业务应用或系统组件等yaml资源模板与配置文件都需要定期备份,我们使用了开源的kube-backup,此工具以cronjob的方式运行于集群中,并采用ssh或AWS CodeCommit认证方式,定期的将所有配置的资源将按照$namespace/$name.$type.yaml文件结构的格式以YAML格式导出到目录树结构中,并同步推送到 gitlab或 github上。
有了版本管理工具,我们就可以借助其diff功能,查看每个时间段产生的配置变更信息,也可以方便的进行回滚恢复。
在生产中,推荐如下的使用方式,即不同的分支对应不同的K8S集群的数据备份,以方便管理与使用。
• 备份集群以及恢复
• copy当前集群的资源到其他集群
• 复制产品环境到开发以及测试环境中
◎ velero包含的额组件
• server运行在你的集群中
• 运行在本地的命令行客户端工具
◎ velero支持的备份存储provider
• aws s3(以及s3兼容的存储,比如可以使用minio)
• azure blob存储
• google cloud存储
4 k8s卷存储备份与恢复
存储备份可以分为本地和网络存储两种,网络存储备份,一般是借助于网络存储系统进行,比如Ceph,GlusterFS, NFS等,具体本节不展开,请参考网上相关方法。
4.1 PV备份与恢复
PV(PersisentVolume)是K8S为有状态服务提供持久化需求的一种能力,当用户的数据以PV卷存放时,定期备份是很重要的。
对数据卷的备份,我们可以使用velero工具进行,比如:当我们的PV被删除后,需要恢复的话,velero就可以轻松完成,它集成了restic,用以备份容器的persistentvolumes。使用velero备份persistentvolumes时需要persistentvolumes支持snapshots功能(快照功能通常由AmazonEBS Volumes,AzureManagedDisks,GooglePersistentDisks等提供),否则需要单独使用restic。
4.2 容器数据备份与迁移
这里简单介绍一下容器数据备份与迁移场景,并介绍下采用的工具。
容器数据迁移场景,本质也是先备份后恢复与同步,这种场景很多,比如当我们需要将一个老平台的某个有状态的业务迁移到现有新容器平台时,我们先提前在新平台创建该业务,然后再将数据进行同步,我们就可以使用rsync工具,例如:rsync-ausername@remote_host:/home/username/dir1place_to_sync_on_local_machine,rsync的好处可以异步同步数据,等数据同步好后,可以在某个低谷时期,进行新老平台业务的切换,最后关闭旧容器平台相应的业务,从而完成有状态服务的迁移。
本文是课程《容器云平台的备份恢复与容灾》中的一部分,该课程还包括容器平台跨数据中心高可用、容器平台跨地域高可用、容器平台数据备份与恢复、多云备份与云上容灾等,是一篇优秀的系统教程,点击阅读原文可下载
觉得本文有用,请转发或点击“在看”,让更多同行看到
资料/文章推荐:
欢迎关注社区 “容器云”技术主题 ,将会不断更新优质资料、文章。地址:https://www.talkwithtrend.com/Topic/98447
下载 twt 社区客户端 APP
长按识别二维码即可下载
或到应用商店搜索“twt”
长按二维码关注公众号
*本公众号所发布内容仅代表作者观点,不代表社区立场